# react基础面试题

# 1、react hooks与class组件的区别在于什么?

Hook 增加了函数式组件中 state 的使用,在之前函数式组件是无法拥有自己的状态,只能通过 props 以及 context 来渲染自己的 UI,而在业务逻辑中,有些场景必须要使用到 state,那么我们就只能将函数式组件定义为 class 组件。而现在通过 Hook,我们可以轻松的在函数式组件中维护我们的状态,不需要更改为 class 组件。

类组件的不足:

  • 状态逻辑难以复用
    • 缺少复用机制
    • 渲染属性和高阶组件会导致层级冗余
  • 趋向复杂难以维护
    • 生命周期函数混杂不相干逻辑
    • 相干逻辑分散在不同生命周期函数中
  • this 指向困扰
    • 内联函数过度创建新句柄
    • 类成员函数不能保证 this

Hooks 的优势:

  • 优化类组件的三大问题
    • 函数组件无 this 问题
    • 自定义 Hook 方便复用状态逻辑
    • 副作用的关注点分离

# 2、react16新生命周期,有什么变化?

react 16.3版本出现了两个新的生命周期函数,并将逐渐废弃componentWillMount()、componentWillReceiveProps()、componentWillUpdate()

1、static getDerivedStateFromProps(props, state)

最常见的误解就是 getDerivedStateFromProps 和 componentWillReceiveProps 只会在 props “改变”时才会调用。实际上只要父级重新渲染时,这两个生命周期函数就会重新调用,不管 props 有没有“变化”。即挂在和更新阶段都可能会调用。

getDerivedStateFromProps 的存在只有一个目的:让组件在 props 变化时更新 state

2、getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()

此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等

# 3、diff算法、key作用,不要key会怎样?

树diff、组件diff、元素diff;

key是给每一个 vnode 的唯一id,可以依靠key,更准确, 更快的拿到oldVnode中对应的vnode节点。

  1. 更准确,因为带key就不是就地复用了,在sameNode函数 a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。

  2. 更快,利用key的唯一性生成map对象来获取对应节点,比遍历方式更快。(这个观点,就是我最初的那个观点。从这个角度看,map会比遍历更快。)

# 4、react生命周期介绍,怎么执行。说一下下面的组件生命周期执行顺序【描述】有这样的组件

React 组件的生命周期有三个不同的阶段:

  • 初始渲染阶段:这是组件即将开始其生命之旅并进入 DOM 的阶段。
  • 更新阶段:一旦组件被添加到 DOM,它只有在 prop 或状态发生变化时才可能更新和重新渲染,这些只发生在这个阶段。
  • 卸载阶段:这是组件生命周期的最后阶段,组件被销毁并从 DOM 中删除。

首次渲染

1 father constructor
2 father getDerivedStateFromProps
3 father render
4 children constructor
5 children getDerivedStateFromProps
6 children render
7 children componentDidMount
8 father componentDidMount
1
2
3
4
5
6
7
8

父组件数据修改触发重渲染:

1 father getDerivedStateFromProps
2 father shouldComponentUpdate
3 father render
4 children getDerivedStateFromProps
5 children shouldComponentUpdate
6 children render
7 children getSnapshotBeforeUpdate
8 father getSnapshotBeforeUpdate
9 children componentDidUpdate, snapshot: 1
10 father componentDidUpdate, snapshot: 1
1
2
3
4
5
6
7
8
9
10

父组件调用 forceUpdate:

1 father getDerivedStateFromProps
2 father render
3 children getDerivedStateFromProps
4 children shouldComponentUpdate
5 children render
6 children getSnapshotBeforeUpdate
7 father getSnapshotBeforeUpdate
8 children componentDidUpdate, snapshot: 1
9 father componentDidUpdate, snapshot: 1
1
2
3
4
5
6
7
8
9

销毁:

1 father componentWillUnmount
2 children componentWillUnmount
1
2

旧生命周期在各个阶段的调用情况:

挂载:

constructor componentWillMount render componentDidMount

更新: componentWillReceiveProps shouldComponentUpdate componentWillUpdate render componentDidUpdate

销毁: componentWillUnmount

新生命周期在各个阶段的调用情况

挂载:

constructor getDerivedStateFromProps render componentDidMount

更新:

getDerivedStateFromProps shouldComponentUpdate render getSnapshotBeforeUpdate componentDidUpdate

卸载:

componentWillUnmount

父子组件之间的生命周期函数的调用顺序:

挂载阶段,只有当执行到 render 的时候,父组件的 constructor 才开始执行,直到子组件挂载完成(componentDidMount),父组件才算挂载完成

更新阶段,类似挂载阶段,只有父组件执行到 render,才开始子组件的 getDerivedStateFromProps -> shouldComponentUpdate -> render,但再父组件的 getSnapshotBeforeUpdate 是紧随在子组件的 getSnapshotBeforeUpdate 后,然后子组件在 componentDidUpdate

父组件调用 forceUpdate,组件调用 forceUpdate 方法后,不会执行 shouldComponentUpdate,会执行 getDerivedStateFromProps,然后再 render,后面的生命周期和更新一致

# 5、setState到底是异步还是同步?

setState并不是单纯的异步或同步,这其实与调用时的环境相关

  1. setState只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout 中都是同步的。

  2. setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的 callback 拿到更新后的结果。

  3. setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和 setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState,setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。

# 6、为什么不能条件使用 react hooks

确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。

# 7、什么是副作用,为什么说函数式组件没有副作用

纯函数(Pure function):给一个 function 相同的参数,永远会返回相同的值,并且没有副作用;这个概念拿到 React 中,就是给一个 Pure component 相同的 props, 永远渲染出相同的视图,并且没有其他的副作用;纯组件的好处是,容易监测数据变化、容易测试、提高渲染性能等;

副作用(Side Effect)是指一个 function 做了和本身运算返回值无关的事,比如:修改了全局变量、修改了传入的参数、甚至是 console.log(),所以 ajax 操作,修改 dom 都是算作副作用的;

react 副作用的时机(生命周期函数):

  • Mount 之后 - componentDidMount
  • Update 之后 - componentDidUpdate
  • Unmount 之前 - componentWillUnmount

# 8、redux的数据流工作流程

  1. 首先,用户(通过View)发出Action,发出方式就用到了dispatch方法。

  2. 然后,Store自动调用Reducer,并且传入两个参数:当前State和收到的Action,Reducer会返回新的State

  3. State一旦有变化,Store就会调用监听函数,来更新View。

# 9、React setState 笔试题,下面的代码输出什么?

class Example extends React.Component {
  constructor() {
    super();
    this.state = {
      val: 0
    };
  }
  
  componentDidMount() {
    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 1 次 log

    this.setState({val: this.state.val + 1});
    console.log(this.state.val);    // 第 2 次 log

    setTimeout(() => {
      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 3 次 log

      this.setState({val: this.state.val + 1});
      console.log(this.state.val);  // 第 4 次 log
    }, 0);
  }

  render() {
    return null;
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

1、第一次和第二次都是在 react 自身生命周期内,触发时 isBatchingUpdates 为 true,所以并不会直接执行更新 state,而是加入了 dirtyComponents,所以打印时获取的都是更新前的状态 0。

2、两次 setState 时,获取到 this.state.val 都是 0,所以执行时都是将 0 设置成 1,在 react 内部会被合并掉,只执行一次。设置完成后 state.val 值为 1。

3、setTimeout 中的代码,触发时 isBatchingUpdates 为 false,所以能够直接进行更新,所以连着输出 2,3。

输出: 0 0 2 3

# React组件通信如何实现?

  • 父组件向子组件通讯: 父组件可以向子组件通过传 props 的方式,向子组件进行通讯 子组件向父组件通讯: props+回调的方式,父组件向子组件传递props进行通讯,此props为作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息,作为参数,传递到父组件的作用域中

  • 兄弟组件通信: 找到这两个兄弟节点共同的父节点,结合上面两种方式由父节点转发信息进行通信

  • 跨层级通信: Context设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言,对于跨越多层的全局数据通过Context通信再适合不过

  • 发布订阅模式: 发布者发布事件,订阅者监听事件并做出反应,我们可以通过引入 event 模块进行通信

  • 全局状态管理工具: 借助Redux或者Mobx等全局状态管理工具进行通信,这种工具会维护一个全局状态中心Store,并根据不同的事件产生新的状态

# 你是如何理解fiber的?

React Fiber 是 React 框架的一种新的协调引擎,用于实现&优化 React 组件的更新和渲染。它是在 React 16 版本中引入的重要特性。React Fiber 的目标是提供更好的用户体验,使 React 应用在处理大型、复杂的组件树时能够更加高效、流畅地进行更新。

  1. 可中断的更新:之前 React 在做组件更新和渲染时是不可中断的,这会导致一些长时间运行的任务会卡住主线程,影响交互性能。React Fiber 可以分割任务,在空闲时间进行小块更新。它随时能够停止,恢复。停止恢复的时机取决于当前的一帧(16ms)内,还有没有足够的时间允许计算。
  2. 更好的优先级:React Fiber 给不同类型的更新指定了优先级,根据任务的重要程度和紧急程度进行调度,确保关键任务能够优先执行,提升用户体验。比如用户输入事件优先级最高,动画优先级也比数据更新优先级高,这样可以保证界面的响应性。
  3. 更好的异常处理:以前的异常处理不是很稳定,React Fiber 中精心设计的错误边界 (Error Boundaries) 机制可以很好地处理错误,而不会影响整个应用。
  4. 更高效的渲染方式:React Fiber 使用新的协调 (Reconciliation) 算法, 可以记录组件的更新过程和依赖关系,从而实现更高效的渲染和协调,避免不必要的重复渲染。
  5. 支持新的并发特性:基于 React Fiber 架构开发的 React 能更好地支持未来的并发渲染、时间分片、暂停/恢复等新特性。

总的来说,React Fiber 是 React 框架的一项重要改进,通过引入新的协调引擎和调度算法,提高了 React 应用在大型、复杂场景下的性能和响应能力,为开发者提供了更好的用户体验,并且为React 未来支持更多平台和更复杂应用打下了基础。

# fiber 有没有什么不足或者坑?

虽然Fiber算法在提高React的性能和用户响应度方面带来了许多好处,但也存在一些潜在的问题和挑战。以下是一些常见的问题和坑:

  1. 异步渲染错误处理:Fiber算法引入了异步渲染概念,这可能导致错误处理变得更加复杂。由于渲染过程被分割成多个任务,错误可能发生在不同的任务中,因此跟踪和处理错误可能会变得更加困难。

  2. 生命周期变更:Fiber算法对React组件的生命周期进行了重构,引入了新的生命周期方法。这意味着一些旧的生命周期方法(如componentWillReceiveProps和componentWillUpdate)被标记为不安全,可能会在未来的版本中被移除。这需要开发人员注意并及时更新代码,处理好组件各生命周期函数中的副作用。

  3. 第三方库兼容性:由于Fiber算法对React的内部实现进行了重构,一些第三方库可能需要进行适应性调整才能与新的Fiber架构兼容。在使用第三方库时,需要确保其与React的版本兼容,并及时更新库的版本。

  4. 调试和开发工具:Fiber算法的引入也带来了对调试和开发工具的一些挑战。由于渲染过程被分割成多个任务,传统的调试技术(如断点调试)可能无法准确地捕获渲染过程中的状态和变化。React团队已经在不断改进调试工具,但仍可能存在一些限制。

  5. 性能调优复杂性:虽然Fiber算法旨在提高性能,但在实际应用中,合理配置和调优仍然是一项复杂的任务。正确设置任务优先级和时间切片,避免过度分片和频繁中断,需要开发人员对应用程序的特定需求和性能特征有深入了解。

  6. Memory leak(内存泄漏):由于Fiber树的引入,如果没有正确处理销毁,可能会导致Fiber节点未被正确释放,造成内存泄漏。需要注意组件的卸载和销毁过程。

  7. First load slow(首次加载慢):使用Fiber后的React应用首屏加载可能会较之前稍慢,这是由于Fiber构建过程的额外工作。可以通过优化打包、使用骨架屏等方式改善。

Strict mode bugs(严格模式bug):在strict mode下可能会暴露出一些问题,需要处理好组件各生命周期函数中的副作用。

# 你对 Time Slice(时间分片) 的理解?

时间分片是 React Fiber 架构中的一个关键概念。它允许 React 应用在渲染大型更新时保持响应性。通过将长时间运行的任务分解成多个小任务,React 可以在执行这些小任务的间隙处理用户交互,如点击、滚动等。

工作原理:

  • 分割任务: React 将更新任务分割成多个小块。
  • 调度: React 调度器会根据任务的优先级和浏览器的空闲时间来决定何时执行这些小任务。 中断与恢复: 如果出现更高优先级的任务(如用户输入),React 可以暂停当前正在进行的渲染任务,
  • 优先处理高优先级任务,然后再恢复之前的渲染。

优点:

  • 更平滑的用户界面: 避免长时间的渲染任务阻塞浏览器,从而提供更流畅的用户体验。
  • 更好的用户交互响应: 应用能更快地响应用户操作,即使在复杂的界面更新中也是如此。

React Fiber 和时间分片共同提高了 React 应用的性能和响应性,特别是在处理大规模更新和复杂动画时。

# React Hooks 常用的有哪些

  • useState: useState 是最常用的 Hook 之一,它允许您在函数组件中使用状态。它返回一个状态值和一个更新状态的函数。您可以使用数组解构来获取这两个值。例如:const [count, setCount] = useState(0);

  • useEffect: useEffect 允许您在组件渲染后执行副作用操作,比如订阅数据、处理 DOM 操作等。它接收一个回调函数和一个依赖数组,用于指定在哪些依赖项发生变化时触发副作用。例如:useEffect(() => { // 副作用操作 }, [dependency]);

  • useContext: useContext 允许您在函数组件中访问 React 的上下文(context)。它接收一个上下文对象(通过 React.createContext 创建),并返回当前上下文的值。例如:const value = useContext(MyContext);

  • useReducer: useReducer 是 useState 的替代方案,用于管理复杂的状态逻辑。它接收一个 reducer 函数和初始状态,并返回当前状态和一个 dispatch 函数用于触发状态更新。例如:const [state, dispatch] = useReducer(reducer, initialState);

  • useRef: useRef 返回一个可变的 ref 对象,可以在多次渲染之间保持稳定。它通常用于获取 DOM 元素的引用或保存任意可变值。例如:const refContainer = useRef(initialValue);

  • useCallback: useCallback 用于优化函数的性能,它返回一个记忆化的函数。当依赖项发生变化时,它会返回一个新的函数引用,否则会返回之前缓存的函数引用。例如:const memoizedCallback = useCallback(() => { // 函数体 }, [dependency]);

  • useMemo: useMemo 用于优化计算的性能,它返回一个记忆化的值。当依赖项发生变化时,它会重新计算值,否则会返回之前缓存的值。例如:const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

# 说说你对HOC(高阶组件)的了解

高阶组件不是组件,是增强函数,可以输入一个元组件,返回出一个新的增强组件; 高阶组件的主要作用有:

  • 代码重用,逻辑和引导抽象
  • 渲染劫持
  • 状态抽象和控制
  • Props 控制

用法:

  • 属性代理 (Props Proxy): 返回出一个组件,它基于被包裹组件进行 功能增强;

    • 默认参数: 可以为组件包裹一层默认参数;
    function proxyHoc(Comp) {
    	return class extends React.Component {
    		render() {
    			const newProps = {
    				name: 'tayde',
    				age: 1,
    			}
    			return <Comp {...this.props} {...newProps} />
    		}
    	}
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    • 提取状态: 可以通过 props 将被包裹组件中的 state 依赖外层,例如用于转换受控组件:
    function withOnChange(Comp) {
    	return class extends React.Component {
    		constructor(props) {
    			super(props)
    			this.state = {
    				name: '',
    			}
    		}
    		onChangeName = () => {
    			this.setState({
    				name: 'dongdong',
    			})
    		}
    		render() {
    			const newProps = {
    				value: this.state.name,
    				onChange: this.onChangeName,
    			}
    			return <Comp {...this.props} {...newProps} />
    		}
    	}
    }
    // 使用姿势如下,这样就能非常快速的将一个 Input 组件转化成受控组件。
    const NameInput = props => (<input name="name" {...props} />)
    export default withOnChange(NameInput)
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
  • 包裹组件: 可以为被包裹元素进行一层包装

    • 包裹组件: 可以为被包裹元素进行一层包装
    function withMask(Comp) {
      return class extends React.Component {
          render() {
    		  return (
    		      <div>
    				  <Comp {...this.props} />
    					<div style={{
    					  width: '100%',
    					  height: '100%',
    					  backgroundColor: 'rgba(0, 0, 0, .6)',
    				  }} 
    			  </div>
    		  )
    	  }
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  • 反向继承 (Inheritance Inversion): 返回出一个组件,继承于被包裹组件,常用于以下操作:

function IIHoc(Comp) {
    return class extends Comp {
        render() {
            return super.render();
        }
    };
}
1
2
3
4
5
6
7
  • 渲染劫持 (Render Highjacking),例如条件渲染
function withLoading(Comp) {
    return class extends Comp {
        render() {
            if(this.props.isLoading) {
                return <Loading />
            } else {
                return super.render()
            }
        }
    };
}
1
2
3
4
5
6
7
8
9
10
11
  • 操作状态 (Operate State): 可以直接通过 this.state 获取到被包裹组件的状态,并进行操作。但这样的操作容易使 state 变得难以追踪,不易维护,谨慎使用。

应用场景:

  • 权限控制,通过抽象逻辑,统一对页面进行权限判断,按不同的条件进行页面渲染
function withAdminAuth(WrappedComponent) {
    return class extends React.Component {
		constructor(props){
			super(props)
			this.state = {
		    	isAdmin: false,
			}
		} 
		async componentWillMount() {
		    const currentRole = await getCurrentUserRole();
		    this.setState({
		        isAdmin: currentRole === 'Admin',
		    });
		}
		render() {
		    if (this.state.isAdmin) {
		        return <Comp {...this.props} />;
		    } else {
		        return (<div>您没有权限查看该页面,请联系管理员!</div>);
		    }
		}
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  • 性能监控,包裹组件的生命周期,进行统一埋点:
function withTiming(Comp) {
    return class extends Comp {
        constructor(props) {
            super(props);
            this.start = Date.now();
            this.end = 0;
        }
        componentDidMount() {
            super.componentDidMount && super.componentDidMount();
            this.end = Date.now();
            console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`);
        }
        render() {
            return super.render();
        }
    };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  • 代码复用,可以将重复的逻辑进行抽象。

# 什么是错误边界?

什么是错误边界

在 React 中,我们通常有一个组件树。如果任何一个组件发生错误,它将破坏整个组件树。没有办法捕捉这些错误,我们可以用错误边界优雅地处理这些错误。

错误边界有两个作用:

  • 如果发生错误,显示回退UI
  • 记录错误

如果类实现了 getDerivedStateFromError 或 componentDidCatch 这两个生命周期方法的任何一个,那么这个类就会成为 ErrorBoundary 。前者返回 {hasError: true} 来呈现回退UI,后者用于记录错误。

# react 和 vue 的异步组件是怎么加载的?

Vue 和 React 都支持加载异步组件,使得应用程序在需要时才动态加载和渲染组件,从而提高应用的性能和加载速度。

在 Vue 中,可以使用 Vue 的异步组件功能来实现动态加载组件。Vue 提供了两种方式来定义异步组件:

  1. 使用 import() 语法和 resolve 函数:可以使用 import() 语法来动态导入组件的模块,并通过 resolve 函数来指定组件的路径。这样在需要使用组件时,Vue 会自动异步加载组件的代码和模块。

    Vue.component('AsyncComponent', () => import('./AsyncComponent.vue'));
    
    1
  2. 使用 component 函数的 resolve 属性:可以在组件定义中使用 component 函数,并通过 resolve 属性指定组件的路径。这样在需要使用组件时,Vue 会自动异步加载组件的代码和模块。

    Vue.component('AsyncComponent', (resolve) => {
      require(['./AsyncComponent.vue'], resolve);
    });
    
    1
    2
    3

在 React 中,可以使用 React 的动态导入功能(Dynamic Import)来实现异步加载组件。React 使用 import() 语法来动态导入组件的模块,并返回一个 Promise 对象。可以使用 React.lazy() 函数和 Suspense 组件来处理异步加载的组件。

const AsyncComponent = React.lazy(() => import('./AsyncComponent'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <AsyncComponent />
      </Suspense>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11

在上述代码中,React.lazy() 函数接受一个返回 Promise 的函数作为参数,该函数使用 import() 语法来动态导入组件的模块。然后,可以将异步加载的组件包裹在 Suspense 组件中,并通过 fallback 属性指定在组件加载过程中显示的占位内容。

需要注意的是,在使用异步组件时,需要考虑到加载过程中的错误处理和加载状态的展示。在 Vue 中,可以使用 <keep-alive> 组件来缓存异步组件,以避免重复加载。在 React 中,可以使用 ErrorBoundary 组件来捕获异步组件加载过程中的错误。

总结:Vue 和 React 都支持异步加载组件的功能,可以根据具体的框架和语法规范使用相应的语法来实现异步组件的动态加载和渲染。

# vue 和 react 中父组件怎么调用子组件的某个方法

在 Vue 和 React 中,父组件可以通过不同的方式来调用子组件的某个方法。

在 Vue 中,可以使用以下方式来调用子组件的方法:

  1. 使用 ref 引用子组件:在父组件中,可以使用 ref 来引用子组件,并通过引用调用子组件的方法。

    <template>
      <div>
        <ChildComponent ref="childRef" />
        <button @click="callChildMethod">Call Child Method</button>
      </div>
    </template>
    
    <script>
    import ChildComponent from './ChildComponent.vue';
    
    export default {
      components: {
        ChildComponent,
      },
      methods: {
        callChildMethod() {
          this.$refs.childRef.childMethod();
        },
      },
    };
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    在上述代码中,通过给子组件添加 ref 属性,可以在父组件中使用 $refs 对象来引用子组件。然后,通过引用调用子组件的方法。

  2. 使用事件机制:子组件可以定义一个自定义事件,并在需要的时候触发该事件。父组件可以监听子组件的事件,并在事件处理函数中调用子组件的方法。

    <template>
      <div>
        <ChildComponent @customEvent="handleChildEvent" />
      </div>
    </template>
    
    <script>
    import ChildComponent from './ChildComponent.vue';
    
    export default {
      components: {
        ChildComponent,
      },
      methods: {
        handleChildEvent() {
          // 调用子组件的方法
        },
      },
    };
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    在上述代码中,子组件可以通过 $emit 方法触发自定义事件 customEvent,父组件可以在模板中使用 @customEvent 来监听子组件的事件,并在事件处理函数中调用子组件的方法。

在 React 中,可以使用以下方式来调用子组件的方法:

  1. 使用 ref 引用子组件:在父组件中,可以使用 ref 来引用子组件,并通过引用调用子组件的方法。

    class ParentComponent extends React.Component {
      constructor(props) {
        super(props);
        this.childRef = React.createRef();
      }
    
      callChildMethod() {
        this.childRef.current.childMethod();
      }
    
      render() {
        return (
          <div>
            <ChildComponent ref={this.childRef} />
            <button onClick={() => this.callChildMethod()}>Call Child Method</button>
          </div>
        );
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    在上述代码中,通过创建 ref 对象并将其赋值给子组件的 ref 属性,可以在父组件中使用 current 属性来引用子组件。然后,通过引用调用子组件的方法。

  2. 通过 props 传递回调函数:父组件可以通过 props 将回调函数传递给子组件,子组件可以在需要的时候调用该回调函数。

    class ParentComponent extends React.Component {
      handleChildMethod() {
        // 处理子组件方法的调用
      }
    
      render() {
        return (
          <div>
            <ChildComponent onChildMethod={this.handleChildMethod} />
          </div>
        );
      }
    }
    
    class ChildComponent extends React.Component {
      componentDidMount() {
        this.props.onChildMethod();
      }
    
      render() {
        return <div>Child Component</div>;
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    在上述代码中,父组件将自己的方法 handleChildMethod 通过 props 传递给子组件的 onChildMethod 属性。子组件可以在适当的时候调用该回调函数。

需要注意的是,无论是 Vue 还是 React,通过 ref 引用子组件的方式只适用于类组件(在 Vue 中也适用于有状态的函数式组件)。对于函数式组件,在 React 中可以使用 useRef 钩子来创建引用。

总结:在 Vue 和 React 中,父组件可以通过 ref 引用子组件并调用子组件的方法,或者通过 props 传递回调函数给子组件,在适当的时候由子组件调用该回调函数。这些方式都允许父组件与子组件进行交互和通信。

# react-router⾥的标签和标签有什么区别?

  1. 路由切换方式:<Link> 组件通过 React Router 库来管理路由切换,它会使用 JavaScript 来处理导航事件,而不会触发浏览器的完整页面刷新。相比之下,<a> 标签是传统的 HTML 元素,点击时会触发浏览器的完整页面刷新。
  2. 阻止默认行为:使用 <Link> 组件时,React Router 会拦截点击事件,并使用浏览器的 History API 来切换路由,而不会导致页面刷新。这样可以避免页面的重新加载,提供更流畅的用户体验。相反,<a> 标签的点击事件会触发浏览器的默认行为,即刷新页面或导航到链接的 URL。
  3. 路由匹配:<Link> 组件会根据指定的路径和路由配置进行匹配,并生成正确的 URL。它会自动处理基于路由配置的路径匹配逻辑,确保生成的链接与当前路由匹配。相比之下,<a> 标签需要手动设置正确的 URL,没有内置的路由匹配功能。